// dynamic_bit_array.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "src/heo/include/alloc_page.h"
#include "src/heo/include/alloc_lite.h"
#include "src/heo/include/alloc_big.h"
#include "src/heo/include/lf_endl.h"
//#include "src/heo/include/random.h"
#include "src/heo/include/dynamic_bit_array.h"
#include "src/heo/include/transmit_dynamic_bit_array.h"
#include "src/heo/include/easy_omp.h"
#include "src/heo/include/unit_tester.h"
#include "src/heo/include/console_color.h"
#include "src/heo/include/stream_visitor.h"

using namespace std;


template<>
struct test<111>
{
    inline bool run()
    {
        dynamic_bit_array<> arr;
        return true;
    }
};

template<>
struct test<222>
{
    inline bool run()
    {
        dynamic_bit_array<> A;
        A.resize(1000);
        A.resize(2000);
        A.resize(0);
        return true;
    }
};

template<>
struct test<333>
{
    inline bool run()
    {
        dynamic_bit_array<> A;
        A.resize(148488);
        ////A.dump(cout);
        ////cout << lf;
        const size_t data[] = {15,17,33,63,65, 148488-1};
        for (int i = 0; i < _countof(data); ++i)
        {
            A.set(data[i]);
        }
        ////A.dump(cout);
        ////cout << lf;
        for (int i = 0; i < _countof(data); ++i)
        {
            VERIFY_(A.get(data[i]));
        }
        size_t k = 0;
        for (size_t i = A.find_first(); i != A.npos; i = A.find_next(i), ++k)
        {
            //__debugbreak();
            ////cout << i << lf;
            VERIFY_(A.get(i));
            VERIFY_(data[k] == i);
        }
        return true;
    }
};

template<>
struct test<444>
{
    inline bool run()
    {
        dynamic_bit_array<> A;
        A.resize(1000, 0, false);
        A.set();
        A.resize(7);
        VERIFY_(A.size() == 7);
        VERIFY_(A.block_count() == 1);
        size_t k = 0;
        for (size_t i = A.find_first(); i != A.npos; i = A.find_next(i), k++)
        {
            VERIFY_(i == k);
        }
        //A.dump(cout);
        //cout << endl;
        return true;
    }
};

class thread_alloc_init
{
public:
    static void enter_Thread(int nRank)
    {
        lite_allocator::enter_Thread(nRank);
        big_allocator::enter_Thread(nRank);
    }
    static void leave_Thread(int nRank)
    {
        lite_allocator::leave_Thread(nRank);
        big_allocator::leave_Thread(nRank);
    }
};


template<class T>
struct omp_fork
{
    T&  t_;

    explicit omp_fork(T& t):
        t_(t)
    {}

    void operator()(omp::communicator& comm)
    {
        int rank = comm.rank();
        if (t_.rank_ == rank)
        {
            t_(comm);
        }
        else
        {
            thread_alloc_init::enter_Thread(rank);
            T x = t_;
            x.rank_ = rank;
            x(comm);
            thread_alloc_init::leave_Thread(rank);
        }
    }

    template<class S>
    void operator()(omp::communicator& comm, S& shared)
    {
        int rank = comm.rank();
        if (t_.rank_ == rank)
        {
            t_(comm, shared);
        }
        else
        {
            thread_alloc_init::enter_Thread(rank);
            T x = t_;
            x.rank_ = rank;
            x(comm, shared);
            thread_alloc_init::leave_Thread(rank);
        }
    }
};

static int g_test = 0;
#pragma omp threadprivate(g_test)

class Foo
{
public:
    int rank_;

    void operator()()
    {
        rank_ = 0;
        int np = omp_get_max_threads();
        omp_fork< Foo > f(*this);
        omp::communicator::run(np, f);
    }

    void operator()(omp::communicator& comm)
    {
        ASSERT_(this->rank_ == comm.rank());
        //int np   = comm.size();
        

#pragma omp critical(debug_out)
        cout << "rank " << rank_ << " = " << g_test << lf;

        g_test = rank_;
    };
};

template<>
struct test<555>
{
    inline bool run()
    {
        Foo f;
        f();
        std::cout << "__________" << lf;
        f();
        std::cout << "__________" << lf;
        f();
        std::cout << "__________" << lf;
        f();
        std::cout << "__________" << lf;
        return true;
    }
};

static int* g_dummy_lite[32] = {};
static int** g_dummy_big[32] = {};
static int g_error = 0;

struct BarShared
{
    int state_;
    int* gt_;
};


class Bar
{
public:
    int rank_;

    void foo(BarShared& bs)
    {
        rank_ = 0;
        int np = omp_get_max_threads();
        omp_fork< Bar > f(*this);
        omp::communicator::run(np, f, bs);
    }

    void operator()(omp::communicator& comm, BarShared& bs)
    {
        ASSERT_(this->rank_ == comm.rank());
        int np   = comm.size();
        int prev = (np + this->rank_ - 1) % np;
        int next = (this->rank_ + 1)      % np;

//#pragma omp critical(debug_out)
        //cout << "rank " << rank_ << " = " << g_test << lf;
        //int ddd = -1;
        dynamic_bit_array<>* pd = new dynamic_bit_array<>();
        pd->resize(148488);
        switch(bs.state_)
        {
        case 1:
            {
                dynamic_bit_array<> a;
                a.resize(148488);
                a.set(1);
                if ((rank_ & 1) == 0)
                {
//#pragma omp critical(debug_out)
//                    {
//                        std::cout << rank_ << ":" << (size_t)a.front_block() << std::endl;
//                    }
                    omp::isend ois(next, comm);
                    ois << a;
                    omp::opid_t id = ois.get_Last_Op_Id();
                    for (;;)
                    {
                        if (comm.is_Op_Complete(next, id))
                            break;
                        sys_api::Yield_Thread();
                        //std::cout << '.';
                    }
//#pragma omp critical(debug_out)
//                    {
//                        std::cout << "a sent" << std::endl;
//                    }
                    delete pd;
                    pd = NULL;
                    big_allocator::squeeze();
                    pd = new dynamic_bit_array<>();
                    pd->resize(148488);
                    pd->set();
//#pragma omp critical(debug_out)
//                    {
//                        std::cout << rank_ << ":" << (size_t)a.front_block() << std::endl;
//                    }
                }
                if ((rank_ & 1) != 0)
                {
                    dynamic_bit_array<> b;
                    //sys_api::Yield_Thread();
                    //sys_api::Sleep(50);
//#pragma omp critical(debug_out)
//                    {
//                        std::cout << rank_ << ":recv()" << std::endl;
//                    }
                    sys_api::Sleep(100);
                    omp::recv(prev, comm) >> b;
                    size_t j = 0;
                    for (; j < a.size(); ++j)
                    {
                        if (a[j] != b[j])
                            break;
                    }
                    bool flag = a.equals(b);
                    VERIFY_(flag == (j >= a.size()));
#pragma omp critical(debug_out)
                    {
                        if (j < a.size())
                        {
                            std::cout << color<ltred, black> << "a != b at " << j << no_color << std::endl;
                            std::cout << "b[0]=" << *b.front_block() << std::endl;
                            ++g_error;
                        }
                        else
                        {
                            std::cout << "a == b" << std::endl;
                        }
                    }
                }
                comm.barrier();
            }
            break;
        //case 3:
        //    g_dummy_lite[rank_] = lite_allocator::Alloc<int>();
        //    if (rank_ != 0)
        //        lite_allocator::Free(g_dummy_lite[rank_]);
        //    g_dummy_big[rank_] = big_allocator::AllocT<int>(1 + (random_generator(rank_).next_SizeT(5000)),
        //        (random_generator(rank_).next_SizeT(500)), false);
        //    if (rank_ != 0)
        //        big_allocator::FreeT(g_dummy_big[rank_]);
            break;
        case 2:
            break;
        case 4:
            //if (0 == rank_)
            //    lite_allocator::Free(g_dummy_lite[rank_]);
            //if (0 == rank_)
            //    big_allocator::FreeT(g_dummy_big[rank_]);
            break;
        }
        if (pd)
            delete pd;
        g_test = rank_;
    }
};


template<>
struct test<777>
{
    inline bool run()
    {
        for (int i = 0; i < 50; ++i)
        {
            BarShared bs = {};
            Bar b;
            b.foo(bs);
            //std::cout << "__________" << lf;
            bs.state_ = 1;
            b.foo(bs);
            //std::cout << "__________" << lf;
            //bs.state_ = 2;
            //b.foo(bs);
            //std::cout << "__________" << lf;
            //bs.state_ = 3;
            //b.foo(bs);
            //std::cout << "__________" << lf;
            //bs.state_ = 4;
            //b.foo(bs);
            //std::cout << "__________" << lf;
        }
        return !g_error;
    }
};

class Bar2
{
public:
    int rank_;

    void foo(BarShared& bs)
    {
        rank_ = 0;
        int np = omp_get_max_threads();
        omp_fork< Bar2 > f(*this);
        omp::communicator::run(np, f, bs);
    }

    void operator()(omp::communicator& comm, BarShared& bs)
    {
        ASSERT_(this->rank_ == comm.rank());
        int np   = comm.size();
        int t = 100 + rank_;
        comm.all_gather(&t, 1, bs.gt_);
        if (0 == rank_)
        {
            meta::ostream_visitor kout(cout);
            kout.visit_Array(bs.gt_, np, "gather", 0);
            cout << endl;
        }
        comm.barrier();
        if (0 != rank_)
        {
            meta::ostream_visitor kout(cout);
            kout.visit_Array(bs.gt_, np, "gather", 0);
            cout << endl;
        }
    }
};

template<>
struct test<8>
{
    inline bool run()
    {
        for (int i = 0; i < 3; ++i)
        {
            BarShared bs = {};
            int np = omp_get_max_threads();
            bs.gt_ = new int[np];
            Bar2 b;
            b.foo(bs);
            delete [] bs.gt_;
        }
        return true;
    }
};

void Mem_Panic(size_t cbRequired, size_t* pcbAvailable)
{
    //TODO fix me...
    std::cout << "mem panic: " << cbRequired << " bytes required" << lf;
    if (pcbAvailable)
        std::cout << "but " << *pcbAvailable << " available" << lf;
}

int main(int argc, char* argv[])
{
    cout << "Hello" << endl;
    int np = omp_get_max_threads();
    VERIFY_(lite_allocator::construct(np, 1));//:memory in MB per thread
    VERIFY_(big_allocator::construct(np, 4));//:memory in MB per thread
    //random_generator::randomize(np, 1100101, 0);
    bool r = tester<16>().run_Test();
    if (r)
        cout << "All right" << endl;

#if(MEM_STAT > 0)
    std::cout << lf << "__________" << lf;
    big_allocator::report(std::cout);
    std::cout << "==========" << lf;
    lite_allocator::report(std::cout);
#endif

    big_allocator::destruct();
    lite_allocator::destruct();
    page_allocator::destruct();

#if defined(_MSC_VER)
    extern void If_Debug_Press_Enter();
    If_Debug_Press_Enter();
#endif
    return 0;
}

